feat(mobile): push notifications MVP - #6269
Conversation
Signed-off-by: Tom Brow <tomb@squareup.com>
c7a089a to
d69b89c
Compare
Signed-off-by: Tom Brow <tomb@block.xyz>
…ifications-pr * origin/main: (33 commits) fix(hooks): scope pre-push lanes to branch merge-base diff (#6423) Enforce a three-day dependency cooldown (#6426) perf(desktop): resolve references without directory scans (#6328) feat(llm): stamp thinking effort on call-completed log line (#6424) Fix cross-owner relay agent mentions in owner-only builds (#6338) feat(cli): accept Buzz message links for thread reads (#6359) feat(workflows): add workflow editor (#6248) fix(desktop): preserve huddle speech boundaries (#6397) test(desktop): use a wordlist-safe separator in passphrase word-count test (#6356) fix(models): curate Databricks alias-aware labels for 5 missing endpoints (#6360) fix(acp): guard against unrequested public relay skills (#6394) feat(desktop): refine context-aware Projects collaboration (#6396) fix(desktop): distinguish duplicate agent devices (#6337) feat(desktop): close Buzz window with Cmd+W (#6314) refactor(prompt): simplify Buzz agent guidance (#6340) feat(desktop): make Projects workspaces selectable (#6368) Add Buzz-native collaboration benchmarks (#6264) Polish mobile timeline and emoji interactions (#6297) feat(desktop): make the Projects overview follow the selected section (#6335) refactor(desktop): coordinate TTS playback (#6341) ... Signed-off-by: Tom Brow <tomb@block.xyz>
Signed-off-by: Tom Brow <tomb@block.xyz>
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
Signed-off-by: Tom Brow <tomb@block.xyz> Co-authored-by: Codex <noreply@openai.com> Ai-assisted: true
…ifications-pr * origin/main: (38 commits) fix(composer): preserve caret when inserting mentions mid-message (#6531) chore(deps): update rust crate async-trait to v0.1.92 (#6094) chore(deps): update dependency sonner to v2.0.8 (#6093) chore(deps): update rust crate http-body-util to v0.1.4 (#5452) chore(deps): update rust crate http to v1.4.2 (#5451) chore(deps): update rust crate futures-util to v0.3.33 (#5448) chore(deps): update rust crate futures to v0.3.33 (#5445) chore(deps): update dependency @tauri-apps/api to v2.11.1 (#5444) chore(deps): update ubuntu:24.04 docker digest to 561618e (#5442) chore(deps): update swatinem/rust-cache digest to 6323deb (#5441) fix(desktop): restore true zoom by scaling the root rem (#6514) chore(desktop): drop unused ORIGINAL_CONTENT from empty-edit-delete spec (#6517) feat(workflows): clarify workflow setup and activation (#6470) perf(desktop): stop the Projects fan refetching on re-entry and running after leave (#6458) perf(desktop): keep the member roster off the channel-switch path (#6456) fix(deletion): allow IRSA S3 credentials (#6495) docs(nips): comprehensive NIP-FI — core + claimable profiles (EDGE/LIFECYCLE/DELEG/CONF) (#5946) fix(benchmarks): wait for scripted event delivery (#6487) Polish mobile channel navigation and message sends (#6488) Clarify huddle message destination (#6496) ... Signed-off-by: Tom Brow <tomb@block.xyz>
Signed-off-by: Tom Brow <tomb@block.xyz>
Signed-off-by: Tom Brow <tomb@block.xyz> Co-authored-by: Codex <noreply@openai.com> Ai-assisted: true
Signed-off-by: Tom Brow <tomb@block.xyz> Co-authored-by: Codex <noreply@openai.com> Ai-assisted: true
Signed-off-by: Tom Brow <tomb@block.xyz>
Signed-off-by: Tom Brow <tomb@block.xyz> Co-authored-by: Codex <noreply@openai.com> Ai-assisted: true
…ifications-pr * origin/main: fix(desktop): emit singular `mention` feed category so alerts route correctly (#6665) fix(mobile): recover stale and shuffled messages (#6691) feat(mobile): browse and join open channels (#6243) show mention counts in channel notifications (#6696) fix(desktop): hide selection formatting tray on composer right-click (#6683) fix(desktop): stabilize members dialog scrolling (#6670) fix(desktop): keep member runtime status off the UI thread (#6445) perf(desktop): persist channel heads, collapse thread reads and reply sends (#6572) Downgrade desktop Huddles to audio protocol v2 (#6610) Polish Huddle participant interactions (#6312) Downgrade mobile Huddles to audio protocol v2 (#6558) perf(desktop): make the Projects surface render-cheap (#6460) refactor(acp): clarify agent prompt sections (#6501) Add mobile Huddles voice MVP (#6056) feat(desktop-messages): keep agents addressed across messages (#6315) fix(desktop): remove Buzz entity link previews (#6512) Signed-off-by: Tom Brow <tomb@block.xyz> # Conflicts: # mobile/lib/features/channels/channels_provider.dart # mobile/lib/shared/auth/auth_provider.dart # mobile/lib/shared/community/community_provider.dart # mobile/test/shared/auth/auth_provider_test.dart # mobile/test/shared/community/community_provider_test.dart # scripts/mobile-worktree-overrides.sh
Signed-off-by: Tom Brow <tomb@block.xyz> Co-authored-by: Codex <noreply@openai.com> Ai-assisted: true
Signed-off-by: Tom Brow <tomb@block.xyz> Co-authored-by: Codex <noreply@openai.com> Ai-assisted: true
Signed-off-by: Tom Brow <tomb@block.xyz>
Signed-off-by: Tom Brow <tomb@block.xyz> Co-authored-by: Codex <noreply@openai.com> Ai-assisted: true
Signed-off-by: Tom Brow <tomb@block.xyz> Co-authored-by: Codex <noreply@openai.com> Ai-assisted: true
Signed-off-by: Tom Brow <tomb@block.xyz> Co-authored-by: Codex <noreply@openai.com> Ai-assisted: true
Signed-off-by: Tom Brow <tomb@block.xyz> Co-authored-by: Codex <noreply@openai.com> Ai-assisted: true
…ifications-pr * origin/main: Centralize replaceable event persistence (#6660) feat(workflows): discover trigger filter values (#6712) feat(desktop): simplify the message action rail (#6529) fix(desktop): restore icon-only remote marker (#6491) fix(ci): prevent poisoned Rust caches (#6618) docs(security): route reports through private advisories (#6728) fix(composer): wrap Buzz chip labels without orphaning icons (#6581) fix(desktop): bound thread /query and surface load errors, not false-empty (#6447) Signed-off-by: Tom Brow <tomb@block.xyz>
Signed-off-by: Tom Brow <tomb@block.xyz> Co-authored-by: Codex <noreply@openai.com> Ai-assisted: true
Signed-off-by: Tom Brow <tomb@block.xyz> Co-authored-by: Codex <noreply@openai.com> Ai-assisted: true
Signed-off-by: Tom Brow <tomb@block.xyz>
Signed-off-by: Tom Brow <tomb@block.xyz> Co-authored-by: Codex <noreply@openai.com> Ai-assisted: true
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 5a5e767ddf
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Signed-off-by: Tom Brow <tomb@block.xyz>
Signed-off-by: Tom Brow <tomb@block.xyz> Co-authored-by: Codex <noreply@openai.com> Ai-assisted: true
Signed-off-by: Tom Brow <tomb@block.xyz>
Signed-off-by: Tom Brow <tomb@block.xyz>
Signed-off-by: Tom Brow <tomb@block.xyz> Co-authored-by: Codex <noreply@openai.com> Ai-assisted: true
Signed-off-by: Tom Brow <tomb@block.xyz> Co-authored-by: Codex <noreply@openai.com> Ai-assisted: true
Signed-off-by: Tom Brow <tomb@block.xyz> Co-authored-by: Codex <noreply@openai.com> Ai-assisted: true
Signed-off-by: Tom Brow <tomb@block.xyz>
Signed-off-by: Tom Brow <tomb@block.xyz> Co-authored-by: Codex <noreply@openai.com> Ai-assisted: true
Signed-off-by: Tom Brow <tomb@block.xyz> Co-authored-by: Codex <noreply@openai.com> Ai-assisted: true
…ifications-pr * origin/main: Add gated security reviews (#6816) fix(desktop): accent-colored mention badges that count thread mentions (#6900) Add Buzz benchmark evaluation layers (#6823) fix(desktop): show edited head content in thread panel (#6887) fix(desktop-tooltip): increase surface contrast (#6897) Deduplicate ACP thread prompt context (#6706) Apply access policy when reusing channel agents (#6838) feat(sidebar): prioritize unread DMs in overflow navigation (#6842) feat(projects): add agent and CLI project-home support (#6590) feat(desktop): restore message quick reactions (#6892) Use paired tags for standing & per-turn context (#6701) fix(cli): preserve signatures in event reads (#6884) refactor(db): finish replaceable event store extraction (#6777) Fix Admin feedback filter overflow (#6825) fix(desktop): stop pulsing addressed agents on send (#6873) fix(desktop): prioritize sidebar channel status (#6861) feat(desktop): hyperlink selected composer text on link paste (#6684) Signed-off-by: Tom Brow <tomb@block.xyz>
jedwards27
left a comment
There was a problem hiding this comment.
:bot: Jude’s code review agent — REQUEST CHANGES at exact head a19ae886f3767395bde0254e1bde7748ab3510f8 (base cdab765748ddfeba10830a9a5331b6157bd321df).
Blocking finding
Failed revocation during community removal/sign-out is not durable.
mobile/lib/shared/community/community_provider.dart:258-284 invokes the lease deactivator and catches failure at 274-278, but then unconditionally deletes the community at 280 and removes it from state. The community record holds the durable pending-tombstone journal. After deletion and restart, bootstrap has no record to retry and may no longer have the identity/community state required to authenticate publication.
Consequently, if final tombstone publication fails while removing a community—or while sign-out removes the active community—the remote lease can continue producing wakes until expiry. The repaired settings opt-out path is durable, but this destructive path bypasses that guarantee.
Required author action
Retain a separate durable revocation record containing everything needed to authenticate and publish the tombstone until relay/gateway acceptance, or fail/block deletion with truthful UI when revocation cannot be recorded safely.
Add deterministic regression coverage proving:
- removal tombstone publication fails;
- removal/sign-out follows the chosen product contract;
- the process/provider restarts;
- revocation retries using retained state;
- acceptance clears the journal; and
- the same sequence works for active-community sign-out.
Resolved prior concerns
The ordinary settings opt-out flow now persists disabled preference plus a pending generation before network I/O, retains failed attempts, retries disabled communities during bootstrap with monotonically advancing generations, and clears only accepted work (mobile/lib/shared/community/community_provider.dart:404-532; mobile/lib/shared/push/push_bootstrap.dart:136-251; mobile/lib/shared/push/push_subscription.dart:320-364). Its failure/restart retry has regression coverage (mobile/test/shared/community/community_provider_test.dart:280-339).
Buzz preference and effective iOS authorization are also now distinct: native reports every authorization state and opens system notification settings (mobile/ios/Runner/AppDelegate.swift:330-400); Dart validates and refreshes status on resume (mobile/lib/shared/push/push_bridge.dart:17-88); settings truthfully exposes denied/unavailable state and recovery (mobile/lib/features/settings/settings_page/notifications_section.dart:11-75) with tests covering denial, lookup failure, resume transition, and external revocation.
Exact-head evidence
At the pinned clean head, team validation passed:
- Flutter full suite: 1,931 tests
- BuzzPushKit: 68 XCTest tests plus 18 Swift Testing tests
buzz-push-gateway: 44 passed, 9 ignored, 0 failedjust mobile-check- Required GitHub checks including Mobile, Mobile Swift, Security, Rust, backend/relay integration, and DCO
Live PR head/base and open state were rechecked immediately before this review. The lone failed Codex Security Review / Mark Previous Review Stale job is advisory and failed on workflow-token permission, not PR behavior.
Confidence gaps (not additional author defects)
No signed physical-device APNs/App Attest delivery or lock-screen/Focus/VoiceOver and foreground/background/terminated tap matrix was reproduced. PostgreSQL-backed gateway concurrency/readiness cases remain among the ignored tests. These need release-owner exact-artifact dogfood and repository-owned integration verification; they do not independently drive this verdict.
This PR implements MVP, iOS-only, NIP-PL-compliant push notifications.
A relay with
BUZZ_PUSH_ENABLEDwill send a push notification for any message that appears in the in-app Notifications tab.Enrollment flow
The first time the client first connects to a relay with
BUZZ_PUSH_ENABLED:sequenceDiagram autonumber participant App as Buzz iOS app participant iOS participant Relay as Buzz relay participant Attest as Apple App Attest participant Gateway as Push gateway App->>Relay: Fetch NIP-11 push capability Relay-->>App: Push profile, current relay public key, and limits par App->>iOS: Request notification permission iOS-->>App: Permission result and App->>iOS: Register for remote notifications iOS-->>App: Device token end App->>Gateway: Request installation challenge Gateway-->>App: Single-use challenge App->>Attest: Attest installation transcript Attest-->>App: Attestation proof App->>Gateway: Enroll device token and proof Gateway-->>App: Installation handle App->>Gateway: Request delegation challenge Gateway-->>App: Single-use challenge App->>Attest: Assert relay-key delegation Attest-->>App: Assertion App->>Gateway: Create delegation Gateway-->>App: Opaque endpoint grant App->>Relay: Publish encrypted push lease and filters Relay-->>App: Lease acknowledgedPush-time flow
When a notification-eligible event is received by the relay:
%%{init: { "sequence": { "actorMargin": 20, "width": 110, "messageMargin": 18, "diagramMarginX": 8, "wrap": true } }}%% sequenceDiagram autonumber participant Relay as Buzz relay participant Gateway as Push gateway participant APNs as Apple Push<br/>Notification service participant iOS participant NSE as Notification service<br/>extension Relay->>Gateway: POST /v1/deliveries/apns<br/>opaque endpoint grant, request ID, expiry, NIP-98 authorization Gateway->>APNs: POST /3/device/{device-token}<br/>topic, request ID, expiry, constant mutable-content payload APNs-->>Gateway: 200 OK: request accepted Gateway-->>Relay: 200 OK: accepted status APNs-->>iOS: Notification: constant reconnect alert<br/>mutable-content = 1 iOS->>NSE: Invoke extension<br/>original notification content NSE->>Relay: POST /query: subscription filters, limit 10<br/>NIP-98 authorization Relay-->>NSE: 200 OK: signed Nostr events<br/>kinds 9, 40002, 45001, or 45003 NSE->>iOS: Complete notification: title, body, subtitle<br/>thread ID, exact-message targetrelay → push gateway → APNs -> NSE -> Notification Center
Known limitations
The APNs wake payload is intentionally constant and opaque: it contains no originating community or message identifier, in keeping with the implemented NIP-PL privacy design.
The Notification Service Extension must therefore reconnect to the relay and resolve eligible messages after each wake. Around overlapping wakes, timing boundaries, or resolution windows, notification presentation may occasionally omit an expected message or display a message more than once.
This best-effort behavior is deliberately accepted for the current implementation and will be measured during the internal rollout to determine whether the user experience is acceptable before any broader deployment; the implementation does not claim exactly-once presentation.
Validation
Live end-to-end hardware validation used an internal remotely hosted development relay and push gateway, the APNs sandbox, and a physical iPhone 12 mini:
simctl pushor simulated notification was used.Final validation with a dogfood-signed artifact and production App Attest/APNs configuration remains a release step.
Independent pre-reviews
7eb3a650b; k3-max also revalidated every remediation and the endpoint-specific App Attest enrollment bound.